#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <EEPROM.h>
#include <Servo.h>
extern "C"
{
#include "wifi_raw.h"
}

#define actionTime 7300                        // 拉绳时间
#define unActionTime 5200                      // 松绳时间
#define stopTime 200                           // 开门后停止时间
#define readyFlagTime 600                      // 无锁时间600s
#define arpReadyFlagTime 180                   // 灵犀解锁无锁认证时间180s
#define apSsid "INIT_UNIT"                     // ap状态下wifi账号
#define apPassword "88888888"                  // ap状态下wifi密码
#define superPassword ""                       // 超级密码
#define maxLength 20                           // 最大长度
#define maxMacNum 5                            // 灵犀解锁最大支持设备数
#define maxMacNumLen 1                         // 灵犀解锁设备数所占内存
#define maxMacAddressLen 30                    // 灵犀解锁设备mac所占内存
#define inputFlagTime 10                       // 激活输入持续时间10s
#define inputFlagRow 4                         // 激活输入按钮所在位置行
#define inputFlagCol 1                         // 激活输入按钮所在位置列
#define maxRow 4                               // 键盘行数
#define maxCol 3                               // 键盘列数
const byte rowPins[maxRow] = {D0, D1, D2, D3}; // 行引脚
const byte colPins[maxCol] = {D5, D6, D7};     // 列引脚
const char keys[maxRow][maxCol] =
    {{'1', '2', '3'},
     {'4', '5', '6'},
     {'7', '8', '9'},
     {'*', '0', '#'}};                                                                // 键盘字符
const String noticeUrl = "https://oapi.dingtalk.com/robot/send?access_token={token}"; // 钉钉机器人通知
const String url = "https://gitee.com/guigui1731/iot-door-locks/raw/master";          // 前端交互页面储存地址

#define eepromAddress_password 0 // 对应变量非易失性存储地址，下同（自动分配勿更改）
#define eepromAddress_ssid maxLength + eepromAddress_password
#define eepromAddress_ssid_password maxLength + eepromAddress_ssid
#define eepromAddress_macNum maxLength + eepromAddress_ssid_password
#define eepromAddress_macAddress maxMacNumLen + eepromAddress_macNum
#define eepromMaxSize maxMacAddressLen + eepromAddress_macAddress

short inputFlag = 0;
short readyFlag = 0;
bool apFlag = 0;
char temporaryToken[maxLength];
char temporaryTokenNum = 0;
char lastKey = '\0';
byte macAddress[maxMacNum][6];
uint8_t macNum = 0;
uint8_t macAuth = maxMacNum;
short timer1 = 3600 * 6;
bool autoGen = true;
String tmepStr = "\0";

ESP8266WebServer server(80);
Servo myServo;

String https(String host, bool isPost = false, String postData = "");

void open()
{
  readyFlag = 0;
  inputFlag = 0;
  digitalWrite(D3, HIGH);
  myServo.write(0);
  delay(actionTime);
  myServo.write(90);
  delay(stopTime);
  myServo.write(180);
  delay(unActionTime);
  myServo.write(90);
  digitalWrite(D3, LOW);
  tmepStr = "门开了";
  https(noticeUrl, true, "{\"msgtype\": \"text\",\"text\": {\"content\":\"" + tmepStr + "\"}}");
}

void my_recv_cb(struct RxPacket *pkt)
{
  if (readyFlag < arpReadyFlagTime - 3 && pkt->rx_ctl.legacy_length == 84 && pkt->data[57] == 0x00 && pkt->data[56] == 0x00)
  { // 判断是否为arp probe(手机等设备在且仅在连上wifi时会发送arp probe包含自己的mac地址)
    for (uint8_t i = 0; i < macNum; i++)
      if (pkt->data[48] == macAddress[i][0] && pkt->data[49] == macAddress[i][1] && pkt->data[50] == macAddress[i][2] && pkt->data[51] == macAddress[i][3] && pkt->data[52] == macAddress[i][4] && pkt->data[53] == macAddress[i][5])
      {
        readyFlag = arpReadyFlagTime;
        macAuth = i;
      }
  }
}

char scanKeys()
{
  char key = '\0';
  for (unsigned int i = 0; i < maxCol; i++) // 将列设置为输入并上拉高电平
    pinMode(colPins[i], INPUT_PULLUP);

  for (unsigned int i = 0; i < maxRow; i++)
  {
    pinMode(rowPins[i], OUTPUT);
    digitalWrite(rowPins[i], LOW);
    for (unsigned int j = 0; j < maxCol; j++)
    {
      if (digitalRead(colPins[j]) == LOW)
      {
        delay(80); // 消抖
        if (digitalRead(colPins[j]) == LOW)
          key = keys[i][j];
      }
    }
    digitalWrite(rowPins[i], HIGH);
  }
  return key;
}

String https(String host, bool isPost, String postData)
{
  String payload = "\0";
  if (WiFi.status() == WL_CONNECTED)
  {
    std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure);
    client->setInsecure(); // 不使用指纹证书登录
    HTTPClient https;
    if (https.begin(*client, host))
    {
      int httpCode = 0;
      if (isPost)
      {
        https.addHeader("Content-Type", "application/json");
        httpCode = https.POST(postData); // 发送POST请求
      }
      else
        httpCode = https.GET(); // 访问url返回的状态码
      if (httpCode > 0)
      {
        // HTTP header has been send and Server response header has been handled
        Serial.print(httpCode);
        // file found at server
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY)
        {
          payload = https.getString();
        }
      }
      else
      {
        payload = "Failed, error: " + https.errorToString(httpCode);
      }
      https.end();
    }
    else
    {
      payload = "Unable to connect";
    }
  }

  return payload;
}

void write_to_eeprom(const char *data, int eepromAddress, short _maxLength)
{
  for (int i = 0; i < _maxLength; i++)
  {
    EEPROM.write(eepromAddress + i, data[i]);
    if (data[i] == '\0')
      break; // 如果遇到字符串结束符，停止写入
  }
  return;
}

String read_from_eeprom(int eepromAddress, short _maxLength)
{
  String s = "";
  if (EEPROM.read(eepromAddress) != 255)
    for (int i = 0; i < _maxLength; i++)
    {
      char c = EEPROM.read(eepromAddress + i);
      if (c == '\0')
        break; // 如果遇到字符串结束符，停止读取
      s += c;
    }
  return s;
}

void writeMacAddress()
{
  int address = eepromAddress_macAddress;
  EEPROM.write(eepromAddress_macNum, macNum);

  for (int row = 0; row < macNum; row++)
  {
    for (int col = 0; col < 6; col++)
    {
      EEPROM.write(address, macAddress[row][col]);
      address++;
    }
  }
}

void monitor()
{
  static String s = "";
  if (inputFlag <= 0) //*被按下激活键盘输入
  {
    pinMode(colPins[inputFlagCol - 1], INPUT_PULLUP);
    pinMode(rowPins[inputFlagRow - 1], OUTPUT);
    digitalWrite(rowPins[inputFlagRow - 1], LOW);
    if (digitalRead(colPins[inputFlagCol - 1]) == LOW)
    {
      inputFlag = inputFlagTime;
      scanKeys();
      s = "";
    }
  }
  else
  {
    if (readyFlag > 0)
      open();
    char key = scanKeys();
    if (lastKey != key)
    {
      if (key != '\0')
      {
        inputFlag = inputFlagTime; // 键盘接受输入时间
        if (key != '*' && key != '#')
        {
          if (key != '0')
            digitalWrite(D3, LOW); // 让继电器充当跳一次模拟键盘反馈
          s += key;
          Serial.println(s);
          if (strcmp(s.c_str(), superPassword) == 0 || strcmp(s.c_str(), read_from_eeprom(eepromAddress_password, maxLength).c_str()) == 0 || (temporaryTokenNum > 0 && strcmp(s.c_str(), temporaryToken) == 0))
          {
            open();
            if (strcmp(s.c_str(), temporaryToken) == 0)
              if (--temporaryTokenNum == 0)
              {
                timer1 = 3600 * 6;
                autoGen = true;
              }
          }
        }
        else if (key == '*')
          s = "";
      }
      lastKey = key;
    }
  }
}

void handleRoot()
{
  if (strcmp("", read_from_eeprom(eepromAddress_password, maxLength).c_str()) == 0)
  {
    String ConfigHtml = "<form action=''method=POST style=display:grid;place-items:center;margin-top:30vh><input name=password placeholder='password' required><br><input name=ssid placeholder='wifi name' required><br><input name=ssidpassword placeholder='wifi password' required><br><input name=mac1 placeholder='mac'><br><input name=mac2 placeholder='mac'><br><input name=mac3 placeholder='mac'><br><input name=mac4 placeholder='mac'><br><input name=mac5 placeholder='mac'><br><input type=submit value=Submit></form>";
    server.send(200, "text/html", ConfigHtml);
  }
  else if (strcmp(server.arg("superpassword").c_str(), superPassword) == 0 || (server.hasArg("password") && strcmp(server.arg("password").c_str(), read_from_eeprom(eepromAddress_password, maxLength).c_str()) == 0))
  {
    if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "Open") == 0)
    {
      open();
      server.send(200, "text/html", "success");
    }
    else if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "Ready") == 0)
    {
      if (readyFlag > 0)
        readyFlag = 0;
      else
        readyFlag = readyFlagTime;
      server.send(200, "text/html", (String)readyFlag);
    }
    else if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "loose") == 0)
    {
      digitalWrite(D3, HIGH);
      myServo.write(180);
      delay(1000);
      myServo.write(90);
      digitalWrite(D3, LOW);
      server.send(200, "text/html", "success");
    }
    else if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "tighten") == 0)
    {
      digitalWrite(D3, HIGH);
      myServo.write(0);
      delay(1000);
      myServo.write(90);
      digitalWrite(D3, LOW);
      server.send(200, "text/html", "success");
    }
    else if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "Switch") == 0)
    {
      tmepStr = https(url + "/switch.html");
      tmepStr.replace("{password}", server.arg("password"));
      server.send(200, "text/html", tmepStr);
      delay(3600);
      tmepStr = "\0";
    }
    else if (server.hasArg("action") && server.hasArg("num") && server.hasArg("data") && server.arg("data") != "" && strcmp(server.arg("action").c_str(), "TemporaryToken") == 0)
    {
      autoGen = false;
      server.arg("data").toCharArray(temporaryToken, maxLength);
      temporaryTokenNum = server.arg("num").toInt();
      server.send(200, "text/html", "success");
    }
    else if (server.hasArg("action") && strcmp(server.arg("action").c_str(), "ResetDev") == 0)
    {
      write_to_eeprom("", eepromAddress_password, maxLength);
      EEPROM.commit();
      server.send(200, "text/html", "success");
      tmepStr = "设备已重置";
      https(noticeUrl, true, "{\"msgtype\": \"text\",\"text\": {\"content\":\"" + tmepStr + "\"}}");
    }
    else
    {
      tmepStr = https(url + "/home.html");
      tmepStr.replace("{password}", server.arg("password"));
      server.send(200, "text/html", tmepStr);
      delay(3600);
      tmepStr = "\0";
    }
  }
  else
    server.send(200, "text/html", https(url + "/login.html"));
}

void handleConfig()
{
  if (strcmp("", read_from_eeprom(eepromAddress_password, maxLength).c_str()) == 0)
  {
    write_to_eeprom(server.arg("password").c_str(), eepromAddress_password, maxLength);
    write_to_eeprom(server.arg("ssid").c_str(), eepromAddress_ssid, maxLength);
    write_to_eeprom(server.arg("ssidpassword").c_str(), eepromAddress_ssid_password, maxLength);
    if (server.hasArg("mac1") && server.arg("mac1") != "")
    {
      sscanf(server.arg("mac1").c_str(), "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
             &macAddress[macNum][0], &macAddress[macNum][1], &macAddress[macNum][2],
             &macAddress[macNum][3], &macAddress[macNum][4], &macAddress[macNum][5]);
      macNum++;
    }
    if (server.hasArg("mac2") && server.arg("mac2") != "")
    {
      sscanf(server.arg("mac2").c_str(), "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
             &macAddress[macNum][0], &macAddress[macNum][1], &macAddress[macNum][2],
             &macAddress[macNum][3], &macAddress[macNum][4], &macAddress[macNum][5]);
      macNum++;
    }
    if (server.hasArg("mac3") && server.arg("mac3") != "")
    {
      sscanf(server.arg("mac3").c_str(), "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
             &macAddress[macNum][0], &macAddress[macNum][1], &macAddress[macNum][2],
             &macAddress[macNum][3], &macAddress[macNum][4], &macAddress[macNum][5]);
      macNum++;
    }
    if (server.hasArg("mac4") && server.arg("mac4") != "")
    {
      sscanf(server.arg("mac4").c_str(), "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
             &macAddress[macNum][0], &macAddress[macNum][1], &macAddress[macNum][2],
             &macAddress[macNum][3], &macAddress[macNum][4], &macAddress[macNum][5]);
      macNum++;
    }
    if (server.hasArg("mac5") && server.arg("mac5") != "")
    {
      sscanf(server.arg("mac5").c_str(), "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
             &macAddress[macNum][0], &macAddress[macNum][1], &macAddress[macNum][2],
             &macAddress[macNum][3], &macAddress[macNum][4], &macAddress[macNum][5]);
      macNum++;
    }
    writeMacAddress();
    EEPROM.commit();
    server.send(200, "text/html", "success");
    WiFi.begin(read_from_eeprom(eepromAddress_ssid, maxLength), read_from_eeprom(eepromAddress_ssid_password, maxLength));
  }
  else
    server.send(200, "text/html", "Illegal Access");
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); // 远离充电口led
  digitalWrite(LED_BUILTIN, LOW);
  myServo.attach(D8);

  randomSeed(analogRead(8));
  Serial.begin(115200);
  EEPROM.begin(eepromMaxSize);

  WiFi.begin(read_from_eeprom(eepromAddress_ssid, maxLength), read_from_eeprom(eepromAddress_ssid_password, maxLength)); // 连接到WiFi网络

  // 向ROM读取灵犀解锁设备mac地址
  int address = eepromAddress_macAddress;
  macNum = EEPROM.read(eepromAddress_macNum);
  if (macNum == 255)
    macNum = 0;

  for (int row = 0; row < macNum; row++)
  {
    for (int col = 0; col < 6; col++)
    {
      macAddress[row][col] = EEPROM.read(address);
      address++;
    }
  }

  // 设置路由
  server.on("/", HTTP_GET, handleRoot);
  server.on("/", HTTP_POST, handleConfig);

  // 启动Web服务器
  server.begin();

  // 设置wifi接收数据回调
  wifi_raw_set_recv_cb(my_recv_cb);

  delay(5000);
  tmepStr = "门锁启动 ip:";
  https(noticeUrl, true, "{\"msgtype\": \"text\",\"text\": {\"content\":\"" + tmepStr + WiFi.localIP().toString() + "\"}}");
}

void loop()
{
  unsigned long currentMillis = millis();
  static unsigned long previousMillis1;

  if (currentMillis - previousMillis1 >= 1000)
  {
    // 一秒运行一次
    if (macAuth < maxMacNum)
    {
      char macString[18];
      for (u8_t i = 0; i < 6; i++)
        sprintf(macString + i * 2, "%02X", macAddress[macAuth][i]);
      macAuth = maxMacNum;
      tmepStr = "认证通过 ";
      https(noticeUrl, true, "{\"msgtype\": \"text\",\"text\": {\"content\":\"" + tmepStr + (String)macString + "\"}}");
    }
    if (readyFlag > 0)
      readyFlag -= 1;
    if (inputFlag > 0)
      inputFlag -= 1;
    if (WiFi.status() != WL_CONNECTED)
    {
      if (apFlag == 0)
      {
        WiFi.softAP(apSsid, apPassword);
        apFlag = 1;
      }
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    }
    else if (apFlag == 1)
    {
      WiFi.softAPdisconnect();
      apFlag = 0;
    }
    else if (digitalRead(LED_BUILTIN) == LOW)
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    // 六小时运行一次
    if (timer1 > 3600 * 6)
    {
      if (autoGen)
      {
        tmepStr = "";
        for (int i = 0; i < 8; ++i)
          tmepStr += String(random(10));
        tmepStr.toCharArray(temporaryToken, maxLength);
        temporaryTokenNum = 1;
        tmepStr = "临时密码(可使用1次) : *";
        https(noticeUrl, true, "{\"msgtype\": \"text\",\"text\": {\"content\":\"" + tmepStr + (String)temporaryToken + "\"}}");
      }
      timer1 = 0;
    }
    else
      timer1++;

    previousMillis1 = currentMillis;
  }
  monitor();
  // 处理Web请求
  server.handleClient();
}
